﻿/*
NPlot - A charting library for .NET

AdapterUtils.cs
Copyright (C) 2003-2005
Matt Howlett

Redistribution and use of NPlot or parts there-of in source and
binary forms, with or without modification, are permitted provided
that the following conditions are met:

1. Re-distributions in source form must retain at the head of each
   source file the above copyright notice, this list of conditions
   and the following disclaimer.

2. Any product ("the product") that makes use NPlot or parts 
   there-of must either:
  
    (a) allow any user of the product to obtain a complete machine-
        readable copy of the corresponding source code for the 
        product and the version of NPlot used for a charge no more
        than your cost of physically performing source distribution,
	    on a medium customarily used for software interchange, or:

    (b) reproduce the following text in the documentation, about 
        box or other materials intended to be read by human users
        of the product that is provided to every human user of the
        product: 
   
              "This product includes software developed as 
              part of the NPlot library project available 
              from: http://www.nplot.com/" 

        The words "This product" may optionally be replace with 
        the actual name of the product.

------------------------------------------------------------------------

THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

*/


using System;
using System.Collections;
using System.Data;

namespace NPlot
{

    /// <summary>
    /// Encapsulates functionality relating to exposing data in various
    /// different data structures in a consistent way.
    /// </summary>
    /// <remarks>It would be more efficient to have iterator style access
    /// to the data, rather than index based, and Count.</remarks>
	public class AdapterUtils
	{

		#region AxisSuggesters

		/// <summary>
		/// Interface for classes that can suggest an axis for data they contain.
		/// </summary>
		public interface IAxisSuggester
		{
			/// <summary>
			/// Calculates a suggested axis for the data contained by the implementing class.
			/// </summary>
			/// <returns>the suggested axis</returns>
			Axis Get();
		}


		/// <summary>
		/// Implements functionality for suggesting an axis suitable for charting 
		/// data in multiple columns of a DataRowCollection.
		/// </summary>
		/// <remarks>This is currently not used.</remarks>
		public class AxisSuggester_MultiColumns : IAxisSuggester
		{			

			DataRowCollection rows_;
			string abscissaName_;


			/// <summary>
			/// Constructor
			/// </summary>
			/// <param name="rows">The DataRowCollection containing the data.</param>
			/// <param name="abscissaName">the column with this name is not considered</param>
			public AxisSuggester_MultiColumns(DataRowCollection rows, string abscissaName)
			{
				rows_ = rows;
				abscissaName_ = abscissaName;
			}


			/// <summary>
			/// Calculates a suggested axis for the DataRowCollection data.
			/// </summary>
			/// <returns>the suggested axis</returns>
			public Axis Get()
			{
				double t_min = double.MaxValue;
				double t_max = double.MinValue;

				System.Collections.IEnumerator en = rows_[0].Table.Columns.GetEnumerator();
				
				while (en.MoveNext())
				{
					string colName = ((DataColumn)en.Current).Caption;
					
					if (colName == abscissaName_)
					{
						continue;
					}

					double min;
					double max;
					if (Utils.RowArrayMinMax(rows_, out min, out max, colName))
					{
						if (min < t_min)
						{
							t_min = min;
						}
						if (max > t_max)
						{
							t_max = max;
						}
					}
				}

				return new LinearAxis(t_min, t_max);
			}
		
		}

	
        /// <summary>
        /// This class gets an axis suitable for plotting the data contained in an IList.
        /// </summary>
        public class AxisSuggester_IList : IAxisSuggester
        {
            private IList data_;

            /// <summary>
            /// Constructor. 
            /// </summary>
            /// <param name="data">the data we want to find a suitable axis for.</param>
            public AxisSuggester_IList(IList data)
            {
                data_ = data;
            }

			/// <summary>
			/// Calculates a suggested axis for the IList data.
			/// </summary>
			/// <returns>the suggested axis</returns>
            public Axis Get()
            {
                double min;
                double max;

                if (Utils.ArrayMinMax(data_, out min, out max))
                {
                    if (data_[0] is DateTime)
                    {
                        return new DateTimeAxis(min, max);
                    }

                    else
                    {
                        return new LinearAxis(min, max);
                    }

                    // perhaps return LogAxis here if range large enough 
                    // + other constraints?
                }

                return new LinearAxis(0.0, 1.0);
            }
        }


        /// <summary>
        /// This class is responsible for supplying a default axis via the IAxisSuggester interface.
        /// </summary>
        public class AxisSuggester_Null : IAxisSuggester
        {
			/// <summary>
			/// Returns a default axis.
			/// </summary>
			/// <returns>the suggested axis</returns>
            public Axis Get()
            {
                return new LinearAxis(0.0, 1.0);
            }
        }


		/// <summary>
		/// This class gets an axis corresponding to a StartStep object. The data on
		/// the orthogonal axis is of course also needed to calculate this.
		/// </summary>
        public class AxisSuggester_StartStep : IAxisSuggester
        {
            StartStep abscissaData_;
            IList ordinateData_;

            /// <summary>
            /// Constructor
            /// </summary>
            /// <param name="axisOfInterest">StartStep object corresponding to axis of interest</param>
            /// <param name="otherAxisData">data of other axis (needed to get count value)</param>
            public AxisSuggester_StartStep(StartStep axisOfInterest, IList otherAxisData)
            {
                ordinateData_ = otherAxisData;
                abscissaData_ = axisOfInterest;
            }

			/// <summary>
			/// Calculates a suggested axis given the data specified in the constructor.
			/// </summary>
			/// <returns>the suggested axis</returns>
            public Axis Get()
            {
                return new LinearAxis(
                    abscissaData_.Start,
                    abscissaData_.Start + (double)(ordinateData_.Count - 1) * abscissaData_.Step);
            }
        }


        /// <summary>
        /// Provides default axis if only data corresponding to orthogonal axis is provided.
        /// </summary>
        public class AxisSuggester_Auto : IAxisSuggester
        {
            
			IList ordinateData_;

            /// <summary>
            /// Constructor
            /// </summary>
            /// <param name="ordinateData">Data corresponding to orthogonal axis.</param>
            public AxisSuggester_Auto(IList ordinateData)
            {
                ordinateData_ = ordinateData;
            }

			/// <summary>
			/// Calculates a suggested axis given the data specified in the constructor.
			/// </summary>
			/// <returns>the suggested axis</returns>
            public Axis Get()
            {
                return new LinearAxis(0, ordinateData_.Count - 1);
            }
        }


        /// <summary>
        /// Provides default axis if only data corresponding to orthogonal axis is provided.
        /// </summary>
        public class AxisSuggester_RowAuto : IAxisSuggester
        {
            DataRowCollection ordinateData_;

            /// <summary>
            /// Construbtor
            /// </summary>
            /// <param name="ordinateData">Data corresponding to orthogonal axis.</param>
            public AxisSuggester_RowAuto(DataRowCollection ordinateData)
            {
                ordinateData_ = ordinateData;
            }

			/// <summary>
			/// Calculates a suggested axis given the data specified in the constructor.
			/// </summary>
			/// <returns>the suggested axis</returns>
            public Axis Get()
            {
                return new LinearAxis(0, ordinateData_.Count - 1);
            }
        }

        /// <summary>
        /// Provides axis for data in a given column of a DataRowCollection.
        /// </summary>
        public class AxisSuggester_Rows : IAxisSuggester
        {
            DataRowCollection rows_;
            string columnName_;

            /// <summary>
            /// Constructor
            /// </summary>
            /// <param name="rows">DataRowCollection containing the data to suggest axis for.</param>
            /// <param name="columnName">the column to get data.</param>
            public AxisSuggester_Rows(DataRowCollection rows, string columnName)
            {
                rows_ = rows;
                columnName_ = columnName;
            }

			/// <summary>
			/// Calculates a suggested axis given the data specified in the constructor.
			/// </summary>
			/// <returns>the suggested axis</returns>
            public Axis Get()
            {
                double min;
                double max;

                if (Utils.RowArrayMinMax(rows_, out min, out max, columnName_))
                {
                    if ((rows_[0])[columnName_] is DateTime)
                    {
                        return new DateTimeAxis(min, max);
                    }

                    else
                    {
                        return new LinearAxis(min, max);
                    }
                }

                return new LinearAxis(0.0, 1.0);
            }
        }


        /// <summary>
        /// Provides axis suggestion for data in a particular column of a DataView.
        /// </summary>
        public class AxisSuggester_DataView : IAxisSuggester
        {
            DataView data_;
            string columnName_;

            /// <summary>
            /// Constructor
            /// </summary>
            /// <param name="data">DataView that contains data to suggest axis for</param>
            /// <param name="columnName">the column of interest in the DataView</param>
            public AxisSuggester_DataView(DataView data, string columnName)
            {
                data_ = data;
                columnName_ = columnName;
            }

			/// <summary>
			/// Calculates a suggested axis given the data specified in the constructor.
			/// </summary>
			/// <returns>the suggested axis</returns>
            public Axis Get()
            {
                double min;
                double max;

                if (Utils.DataViewArrayMinMax(data_, out min, out max, columnName_))
                {
                    if ((data_[0])[columnName_] is DateTime)
                    {
                        return new DateTimeAxis(min, max);
                    }

                    else
                    {
                        return new LinearAxis(min, max);
                    }
                }

                return new LinearAxis(0.0, 1.0);
            }
        }

        #endregion
        #region Counters

        /// <summary>
        /// Interface that enables a dataholding class to report how many data items it holds.
        /// </summary>
        public interface ICounter
        {
            /// <summary>
            /// Number of data items in container.
            /// </summary>
            /// <value>Number of data items in container.</value>
            int Count { get; }
        }

        /// <summary>
        /// Class that provides the number of items in an IList via the ICounter interface.
        /// </summary>
        public class Counter_IList : ICounter
        {
            private IList data_;

            /// <summary>
            /// Constructor
            /// </summary>
            /// <param name="data">the IList data to provide count of</param>
            public Counter_IList(IList data)
            {
                data_ = data;
            }

			/// <summary>
			/// Number of data items in container.
			/// </summary>
			/// <value>Number of data items in container.</value>
            public int Count
            {
                get
                {
                    return data_.Count;
                }
            }
        }

		/// <summary>
		/// Class that returns 0 via the ICounter interface.
		/// </summary>
        public class Counter_Null : ICounter
        {
			/// <summary>
			/// Number of data items in container.
			/// </summary>
			/// <value>Number of data items in container.</value>
            public int Count
            {
                get
                {
                    return 0;
                }
            }
        }

		/// <summary>
		/// Class that provides the number of items in a DataRowCollection via the ICounter interface.
		/// </summary>
        public class Counter_Rows : ICounter
        {
            DataRowCollection rows_;

			/// <summary>
			/// Constructor
			/// </summary>
			/// <param name="rows">the DataRowCollection data to provide count of number of rows of.</param>
            public Counter_Rows(DataRowCollection rows)
            {
                rows_ = rows;
            }

			/// <summary>
			/// Number of data items in container.
			/// </summary>
			/// <value>Number of data items in container.</value>
            public int Count
            {
                get
                {
                    return rows_.Count;
                }
            }
        }

		/// <summary>
		/// Class that provides the number of items in a DataView via the ICounter interface.
		/// </summary>
        public class Counter_DataView : ICounter
        {
            DataView dataView_;

			/// <summary>
			/// Constructor
			/// </summary>
			/// <param name="dataView">the DataBiew data to provide count of number of rows of.</param>
            public Counter_DataView(DataView dataView)
            {
                dataView_ = dataView;
            }

			/// <summary>
			/// Number of data items in container.
			/// </summary>
			/// <value>Number of data items in container.</value>
            public int Count
            {
                get
                {
                    return dataView_.Count;
                }
            }

        }

        #endregion
        #region DataGetters

        /// <summary>
        /// Interface for data holding classes that allows users to get the ith value.
        /// </summary>
		/// <remarks>
		/// TODO: should change this to GetNext() and Reset() for more generality.
		/// </remarks>
		public interface IDataGetter
        {
            /// <summary>
            /// Gets the ith data value.
            /// </summary>
            /// <param name="i">sequence number of data to get.</param>
            /// <returns>ith data value.</returns>
            double Get(int i);
        }

        /// <summary>
        /// Provides data in an IList via the IDataGetter interface.
        /// </summary>
        public class DataGetter_IList : IDataGetter
        {
            private IList data_;
            
            /// <summary>
            /// Constructor
            /// </summary>
            /// <param name="data">IList that contains the data</param>
            public DataGetter_IList(IList data)
            {
                data_ = data;
            }

			/// <summary>
			/// Gets the ith data value.
			/// </summary>
			/// <param name="i">sequence number of data to get.</param>
			/// <returns>ith data value.</returns>
            public double Get(int i)
            {
                return Utils.ToDouble(data_[i]);
            }
        }


        /// <summary>
        /// Provides data in an array of doubles via the IDataGetter interface.
        /// </summary>
        /// <remarks>
        /// A speed-up version of DataDetter_IList; no boxing/unboxing overhead.
        /// </remarks>
		public class DataGetter_DoublesArray : IDataGetter
		{
			private double[] data_;

			/// <summary>
			/// Constructor
			/// </summary>
			/// <param name="data">array of doubles that contains the data</param>
			public DataGetter_DoublesArray(double[] data)          
			{
				data_ = data;
			}
		
			/// <summary>
			/// Gets the ith data value.
			/// </summary>
			/// <param name="i">sequence number of data to get.</param>
			/// <returns>ith data value.</returns>
			public double Get(int i)          
			{
				return data_[i];
			}
		}


		/// <summary>
		/// Provides no data.
		/// </summary>
        public class DataGetter_Null : IDataGetter
        {
			/// <summary>
			/// Gets the ith data value.
			/// </summary>
			/// <param name="i">sequence number of data to get.</param>
			/// <returns>ith data value.</returns>
            public double Get(int i)
            {
                throw new NPlotException( "No Data!" );
            }
        }

		/// <summary>
		/// Provides data points from a StartStep object via the IDataGetter interface.
		/// </summary>
        public class DataGetter_StartStep : IDataGetter
        {
            StartStep data_;

			/// <summary>
			/// Constructor
			/// </summary>
			/// <param name="data">StartStep to derive data from.</param>
            public DataGetter_StartStep(StartStep data)
            {
                data_ = data;
            }

			/// <summary>
			/// Gets the ith data value.
			/// </summary>
			/// <param name="i">sequence number of data to get.</param>
			/// <returns>ith data value.</returns>
            public double Get(int i)
            {
                return data_.Start + (double)(i) * data_.Step;
            }
        }

		/// <summary>
		/// Provides the natural numbers (and 0) via the IDataGetter interface.
		/// </summary>
        public class DataGetter_Count : IDataGetter
        {
			/// <summary>
			/// Gets the ith data value.
			/// </summary>
			/// <param name="i">sequence number of data to get.</param>
			/// <returns>ith data value.</returns>
            public double Get(int i)
            {
                return (double)i;
            }
        }


		/// <summary>
		/// Provides data in a DataRowCollection via the IDataGetter interface.
		/// </summary>
        public class DataGetter_Rows : IDataGetter
        {
            private DataRowCollection rows_;
            private string columnName_;

            /// <summary>
            /// Constructor
            /// </summary>
            /// <param name="rows">DataRowCollection to get data from</param>
            /// <param name="columnName">Get data in this column</param>
            public DataGetter_Rows(DataRowCollection rows, string columnName)
            {
                rows_ = rows;
                columnName_ = columnName;
            }

			/// <summary>
			/// Gets the ith data value.
			/// </summary>
			/// <param name="i">sequence number of data to get.</param>
			/// <returns>ith data value.</returns>
            public double Get(int i)
            {
                return Utils.ToDouble((rows_[i])[columnName_]);
            }
        }

		/// <summary>
		/// Provides data in a DataView via the IDataGetter interface.
		/// </summary>
        public class DataGetter_DataView : IDataGetter
        {
            private DataView data_;
            private string columnName_;

            /// <summary>
            /// Constructor
            /// </summary>
            /// <param name="data">DataView to get data from.</param>
            /// <param name="columnName">Get data in this column</param>
            public DataGetter_DataView(DataView data, string columnName)
            {
                data_ = data;
                columnName_ = columnName;
            }

			/// <summary>
			/// Gets the ith data value.
			/// </summary>
			/// <param name="i">sequence number of data to get.</param>
			/// <returns>ith data value.</returns>
            public double Get(int i)
            {
                return Utils.ToDouble((data_[i])[columnName_]);
            }

        }

		
		/// <summary>
		/// Gets data 
		/// </summary>
		/// <remarks>Note: Does not implement IDataGetter... Currently this class is not used.</remarks>
		public class DataGetter_MultiRows 
		{
			
			DataRowCollection rows_;
			string abscissaName_;
			int abscissaColumnNumber_;

			/// <summary>
			/// Constructor
			/// </summary>
			/// <param name="rows">DataRowCollection to get data from.</param>
			/// <param name="omitThisColumn">don't get data from this column</param>
			public DataGetter_MultiRows(DataRowCollection rows, string omitThisColumn )
			{
				rows_ = rows;
				abscissaName_ = omitThisColumn;

				abscissaColumnNumber_ = rows_[0].Table.Columns.IndexOf( omitThisColumn );
				if (abscissaColumnNumber_ < 0)
					throw new NPlotException( "invalid column name" );
			}

			/// <summary>
			/// Number of data points
			/// </summary>
			public int Count
			{
				get
				{
					return rows_[0].Table.Columns.Count-1;
				}
			}

			/// <summary>
			/// Gets data at a given index, in the given series (column number).
			/// </summary>
			/// <param name="index">index in the series to get data for</param>
			/// <param name="seriesIndex">series number (column number) to get data for.</param>
			/// <returns>the required data point.</returns>
			public double PointAt( int index, int seriesIndex )
			{
				if (seriesIndex < abscissaColumnNumber_)
					return Utils.ToDouble( rows_[index][seriesIndex] );
				else
					return Utils.ToDouble( rows_[index][seriesIndex+1] );
			}

		}


        #endregion

    }
}
